module cacheControl
#(parameter SDRAM_SIZE_WIDTH=0,CACHE_SIZE_WIDTH=0, CACHE_SIZE_DWORDS=0, SET_COUNT=0, SET_WIDTH=0, WAY_COUNT=0,WAY_WIDTH=0
)
(
  input                             clk              ,
  input                             reset_n          ,
  input                             isVirtual,
  input [SDRAM_SIZE_WIDTH-2-1:0]    avs_s0_address,        //    s0.address//不包括低两位 24bit = 16M dword 64M byte    128M虚拟空间？
  input                             avs_s0_rw,             //      .write
  input                             avs_s0_request,        //      .read
  output  [31:0]                    avs_s0_readdata,       //      .readdata
  output                            avs_s0_waitrequest,    //      .waitrequest
  
  input   [31:0]     avs_s0_writedata,      //      .writedata
  input   [3:0]      avs_s0_byteenable,     //      .readdata

  output reg                        sdram_req,
  input                             sdram_ack,
  output            [SET_WIDTH-1:0] curr_set_id,
  output reg        [WAY_COUNT-1:0] current_slot,
  output            [WAY_WIDTH-1:0] current_slot_addr,
  output            [WAY_WIDTH-1:0] cache_hited_addr,
  output [SET_WIDTH+CACHE_SIZE_WIDTH-1:0] cache_addr,
  output reg                        write_enable,
  output reg [SET_WIDTH+CACHE_SIZE_WIDTH-1:0] cache_addr_write,
  output reg [WAY_COUNT-1:0]        cache_write_way_reg,
  output reg                  [3:0] cacheByteena,
  output reg                 [31:0] cacheData,
  input                      [31:0] cache_hit_data,
  output reg                        is_single                   ,
  input      [31:0]                 readBuffer                  ,
  output     [SDRAM_SIZE_WIDTH-1:0] rdwr_sdram_addr             ,//26bit, 64M word
  output reg [SDRAM_SIZE_WIDTH-1:0] rdwr_sdram_single_addr      ,
  output     [31:0]                 write_single_sdram_data     ,
  output     [3:0]                  write_single_sdram_mask     ,
  output reg                        read_or_write               ,

  input   [15:0]     page_table_pos,

  input                        debug_nocache,
  output            reg [31:0] flushCount
);

wire nocache = debug_nocache;

always@(posedge clk or negedge reset_n) begin
  if(!reset_n) begin
    flushCount <= 0;
  end else begin
    if(flushCountPlus1)begin
      flushCount <= flushCount + 1'b1;
    end
  end
end

assign curr_set_id = avs_s0_address[SET_WIDTH+CACHE_SIZE_WIDTH-1:CACHE_SIZE_WIDTH];

localparam ADDR_HIGH_WIDTH = SDRAM_SIZE_WIDTH-2-CACHE_SIZE_WIDTH-SET_WIDTH;
localparam TAG_WIDTH = (1 + 1 + WAY_WIDTH + ADDR_HIGH_WIDTH) * WAY_COUNT;

wire                                            curr_cacheDirty =    cacheDirty[current_slot_addr];
wire                                            current_virtual =  cacheVirtual[current_slot_addr];
wire [ADDR_HIGH_WIDTH-1:0]  current_cacheAddrHigh = cacheAddrHigh[current_slot_addr];


assign cache_addr = {curr_set_id, avs_s0_address[CACHE_SIZE_WIDTH-1:0]};

wire [TAG_WIDTH - 1 : 0] cacheTag_out;
wire [TAG_WIDTH - 1 : 0] cacheTag_in;

wire                             cacheDirty   [0:WAY_COUNT-1];//1
reg                              cacheDirty_in[0:WAY_COUNT-1];//1
wire [WAY_WIDTH-1:0]             cache_life   [0:WAY_COUNT-1];//1
reg  [WAY_WIDTH-1:0]             cache_life_in[0:WAY_COUNT-1];//1
wire                           cacheVirtual   [0:WAY_COUNT-1];//1
reg                            cacheVirtual_in[0:WAY_COUNT-1];//1
wire [ADDR_HIGH_WIDTH-1:0]    cacheAddrHigh   [0:WAY_COUNT-1];//15
reg  [ADDR_HIGH_WIDTH-1:0]    cacheAddrHigh_in[0:WAY_COUNT-1];//15

wire [WAY_COUNT-1:0] cache_hited;
wire [WAY_COUNT-1:0] cache_hited_raw;
wire [WAY_COUNT-1:0] free_cache_temp;

  assign    {
      cacheDirty[1],   cacheDirty[0],
      cache_life[1],   cache_life[0],
    cacheVirtual[1], cacheVirtual[0],
   cacheAddrHigh[1],cacheAddrHigh[0]
  } = cacheTag_out;
  //更新tag
  assign cacheTag_in = {
      cacheDirty_in[1],   cacheDirty_in[0],
      cache_life_in[1],   cache_life_in[0],
    cacheVirtual_in[1], cacheVirtual_in[0],
   cacheAddrHigh_in[1],cacheAddrHigh_in[0]
  };

reg set_cache_life;
reg set_cacheAddrHigh;
reg set_cacheDirty;

wire cacheTag_we = set_cache_life | set_cacheAddrHigh | set_cacheDirty;

wire [ADDR_HIGH_WIDTH-1:0] avs_s0_address_high = avs_s0_address[SDRAM_SIZE_WIDTH-2-1:CACHE_SIZE_WIDTH+SET_WIDTH];

genvar i;
generate
  for(i=0; i<WAY_COUNT; i=i+1) begin:BLOCK0
      
    assign cache_hited_raw[i] = avs_s0_address_high == cacheAddrHigh[i] && cacheVirtual[i] == isVirtual;
    assign free_cache_temp[i] = cache_life[i] == 0;

    always@(*) begin
      if(current_slot[i])begin
        
        if(set_cacheDirty)begin
          cacheDirty_in[i] <= 1;
        end else if(set_cacheAddrHigh)begin
          cacheDirty_in[i] <= 0;
        end else begin
          cacheDirty_in[i] <= cacheDirty[i];
        end
      
        if(set_cache_life || set_cacheAddrHigh)begin
          cache_life_in[i] <= 1;
        end else begin
          cache_life_in[i] <= cache_life[i];
        end
        
        if(set_cacheAddrHigh)begin
           cacheVirtual_in[i] <= isVirtual;
          cacheAddrHigh_in[i] <= avs_s0_address_high;
        end else begin
           cacheVirtual_in[i] <= cacheVirtual[i];
          cacheAddrHigh_in[i] <= cacheAddrHigh[i];
        end
        
      end else begin
        if(set_cache_life || set_cacheAddrHigh)begin
          cache_life_in[i] <= 0;
        end else begin
          cache_life_in[i] <= cache_life[i];
        end

           cacheDirty_in[i] <= cacheDirty[i];
         cacheVirtual_in[i] <= cacheVirtual[i];
        cacheAddrHigh_in[i] <= cacheAddrHigh[i];
      end
    end

  end//for
endgenerate

reg  [SET_WIDTH-1:0] curr_set_id_write_reg;

///////////////////////////
initial begin
  $display("fdsa cache tag width, count: %d, %d", TAG_WIDTH, 2**SET_WIDTH);
end
//    cacheDirty    cacheVirtual  cache_life   cacheAddrHigh
reg [TAG_WIDTH-1:0] tagmem[2**SET_WIDTH-1:0];
reg [SET_WIDTH-1:0] address_r_buff;
always @(posedge clk)
begin
    address_r_buff <= curr_set_id;
end
always @(posedge clk)
begin
  if (cacheTag_we) begin
      tagmem[curr_set_id_write_reg] <= cacheTag_in;
  end  
end
assign cacheTag_out = tagmem[address_r_buff];
///////////////////////////

singleOut2 ins_cache_hited(
.in(cache_hited_raw),
.out(cache_hited),
.addr(cache_hited_addr)
);

wire [WAY_COUNT-1:0] free_cache;
singleOut2 ins_free_cache(
  .in(free_cache_temp),
  .out(free_cache),
  .def(1)
);

singleOut2 ins_current_slot_addr(
  .in(current_slot),
  .addr(current_slot_addr)
);


reg [3:0]  interface_status;

localparam STATUS_INIT            = 0;
localparam STATUS_STEP2           = 1;
localparam STATUS_SINGLE          = 2;
localparam STATUS_GET_PHY         = 3;
localparam STATUS_GET_PHY_WAIT    = 4;
localparam STATUS_GET_PHY_WAIT_2  = 5;
localparam STATUS_SINGLE_WAIT     = 6;
localparam STATUS_READ_CACHE      = 7;
localparam STATUS_READ_CACHE_NEXT = 8;
localparam STATUS_READ_CACHE_WAIT = 9;
localparam STATUS_WRITE_BACK      = 10;
localparam STATUS_WRITE_BACK_NEXT = 11;
localparam STATUS_WRITE_BACK_B    = 12;
localparam STATUS_WRITE_BACK_WAIT = 13;
localparam STATUS_WRITE_BACK_END  = 14;


// MMU

//input   [24-1:0]     avs_s0_address
//24+2bit 地址
//[7:0] 高地址
//[7:0] 中地址
//[9:0] 低地址
//总共64M虚拟地址空间

//1: page_table_pos+{8bit高地址,2'b00} 256表项 每个表项32bit，1K byte     取得二级LUT地址
//2: 二级LUT+{中地址,2'b00} 256表项 每个表项32bit，1K byte 取得页地址
//3: 页地址+低地址 = 物理地址 寻址 16bit+10bit = 64Mbyte 物理地址

//一级页表： []

//表项 32bit，低16bit 物理页地址


reg        flushCountPlus1;

assign avs_s0_readdata = direct_read ? readBuffer : cache_hit_data;

assign write_single_sdram_data = avs_s0_writedata;
assign write_single_sdram_mask = ~avs_s0_byteenable;

reg address_type;

wire [SDRAM_SIZE_WIDTH-1:0] base_addr = {(isWriteBack ? current_cacheAddrHigh : avs_s0_address_high), curr_set_id, {CACHE_SIZE_WIDTH{1'b0}}, 2'b0};

assign rdwr_sdram_addr = (address_type == 0) ? 
  base_addr : 
  {physical_addr, base_addr[9:0]};//  / phy_address

reg avs_s0_ack;
wire final_ack = (nocache) ? avs_s0_ack : (ready && (cache_hited!=0));
  
assign avs_s0_waitrequest = (avs_s0_request && !final_ack);

reg isWriteBack;
reg direct_read;
wire [15:0] virtual_addr = isWriteBack == 0 ? avs_s0_address[SDRAM_SIZE_WIDTH-2-1:8] : temp_cache[SDRAM_SIZE_WIDTH-2-CACHE_SIZE_WIDTH-1:1];

wire [SDRAM_SIZE_WIDTH-2-CACHE_SIZE_WIDTH-1:0] temp_cache = {current_cacheAddrHigh, curr_set_id};

reg [15:0] physical_addr;


reg ready;
reg sdram_ack_buff;
always@(posedge clk or negedge reset_n) begin
  if(!reset_n) begin
    sdram_ack_buff <= 0;
    avs_s0_ack <= 0;
    flushCountPlus1 <= 0;
    write_enable <= 0;
    cache_write_way_reg <= 0;

    set_cache_life <= 0;
    set_cacheAddrHigh <= 0;
    set_cacheDirty <= 0;
    curr_set_id_write_reg <= 0;
    
    interface_status <= 0;
    
    sdram_req <= 0;
    is_single <= 0;
    read_or_write <= 0;

    ready <= 0;
    
    current_slot <= 0;
    
    direct_read <= 0;
    address_type <= 0;
    isWriteBack <= 0;
    rdwr_sdram_single_addr <= 0;
  end else begin
    sdram_ack_buff <= sdram_ack;
    avs_s0_ack <= 0;

    flushCountPlus1 <= 0;
    write_enable <= 0;

    set_cache_life <= 0;
    set_cacheAddrHigh <= 0;
    set_cacheDirty <= 0;
    
    curr_set_id_write_reg <= curr_set_id;
    cache_addr_write <= cache_addr;
    cacheByteena <= avs_s0_byteenable;
    cacheData <= avs_s0_writedata;

    ready <= 0;
    case (interface_status)
    STATUS_INIT : begin//初始化
      interface_status <= STATUS_INIT;
      direct_read <= 0;
      if(avs_s0_request && !avs_s0_ack)begin
        ready <= 1;
        if(nocache)begin          //直接读
          interface_status <= STATUS_SINGLE;
        end else begin
          interface_status <= STATUS_STEP2;
        end
      end
    end
    
    STATUS_STEP2: begin
      cache_write_way_reg <= cache_hited;
      if(cache_hited)begin            //再延迟一周期才能判断
        current_slot <= cache_hited;
        set_cache_life <= 1;// 更新tag （命中，当前life=1，另一边置0， 置dirty）
        if(avs_s0_rw)begin
          set_cacheDirty <= 1;
          write_enable <= 1;
          //cache_write_way_reg <= cache_hited;
        end
        interface_status <= STATUS_INIT;
        avs_s0_ack <= 1;
      end else begin
        current_slot <= free_cache;
        interface_status <= STATUS_WRITE_BACK;
      end
    end
    
    STATUS_SINGLE: begin
      rdwr_sdram_single_addr <= {avs_s0_address[SDRAM_SIZE_WIDTH-2-1:8], avs_s0_address[7:0],2'b0};
      sdram_req <= 1;//单读写
      is_single <= 1;
      read_or_write <= avs_s0_rw; // 0:read 1:write
      interface_status <= STATUS_SINGLE_WAIT;//高地址命中等一个周期
    end

    STATUS_SINGLE_WAIT : begin
      interface_status <= STATUS_SINGLE_WAIT;
      if(sdram_ack_buff)begin
        if(!avs_s0_rw)begin
          direct_read <= 1'b1;
        end
        interface_status <= STATUS_INIT;
        avs_s0_ack <= 1;
      end
    end
    
    STATUS_GET_PHY: begin
      rdwr_sdram_single_addr <= {page_table_pos, virtual_addr[15:8], 2'b0};
      sdram_req <= 1;//GET_PHY
      is_single <= 1;
      read_or_write <= 0;
      interface_status <= STATUS_GET_PHY_WAIT;
    end

    STATUS_GET_PHY_WAIT: begin
      if(sdram_ack_buff)begin
        rdwr_sdram_single_addr <= {readBuffer[15:0], virtual_addr[7:0], 2'b0};
      end
      if(!sdram_req && !sdram_ack_buff)begin
        sdram_req <= 1;//GET_PHY
        is_single <= 1;
        read_or_write <= 0;
        interface_status <= STATUS_GET_PHY_WAIT_2;
      end
    end
    
    STATUS_GET_PHY_WAIT_2: begin
      interface_status <= STATUS_GET_PHY_WAIT_2;
      if(sdram_ack_buff)begin
        physical_addr <= readBuffer[15:0];
      end
      if(!sdram_req && !sdram_ack_buff)begin
        interface_status <= isWriteBack == 0 ? STATUS_READ_CACHE_NEXT : STATUS_WRITE_BACK_B;
      end
    end

    STATUS_READ_CACHE: begin
      isWriteBack <= 0;
      if(isVirtual)begin
        interface_status <= STATUS_GET_PHY;
        address_type <= 1;
      end else begin
        interface_status <= STATUS_READ_CACHE_NEXT;
        address_type <= 0;
      end
    end
    
    STATUS_READ_CACHE_NEXT: begin
      sdram_req <= 1;//读缓存
      is_single <= 0;
      read_or_write <= 0;
      interface_status <= STATUS_READ_CACHE_WAIT;
    end
    
    STATUS_READ_CACHE_WAIT: begin
      cache_write_way_reg <= current_slot;
      if(sdram_req == 0)begin
        // 更新tag （未命中，完成读写内存，当前life=1，另一边置0， 更新当前tag地址，）
        set_cacheAddrHigh <= 1;//当前地址写入缓存地址高
        set_cache_life <= 1;
        if(avs_s0_rw)begin
          set_cacheDirty <= 1;
          write_enable <= 1;
          //cache_write_way_reg <= current_slot;
        end
        interface_status <= STATUS_INIT;
        avs_s0_ack <= 1;
      end
    end
    
    
    STATUS_WRITE_BACK: begin
      if(curr_cacheDirty)begin
        interface_status <= STATUS_WRITE_BACK_NEXT;
      end else begin
        interface_status <= STATUS_WRITE_BACK_END;
      end
    end
    
    STATUS_WRITE_BACK_NEXT: begin
      isWriteBack <= 1;//virtual_addr <= {current_cacheAddrHigh,curr_set_id};
      if(current_virtual)begin
        interface_status <= STATUS_GET_PHY;
        address_type <= 1;
      end else begin
        interface_status <= STATUS_WRITE_BACK_B;
        address_type <= 0;
      end
    end
    
    STATUS_WRITE_BACK_B: begin
      sdram_req <= 1;//写缓存
      is_single <= 0;
      read_or_write <= 1;
      flushCountPlus1 <= 1;
      interface_status <= STATUS_WRITE_BACK_WAIT;
    end
    
    STATUS_WRITE_BACK_WAIT: begin
      interface_status <= STATUS_WRITE_BACK_WAIT;
      if(sdram_ack_buff)begin
        interface_status <= STATUS_WRITE_BACK_END;
      end
    end
    
    STATUS_WRITE_BACK_END: begin
      //写回后读取
      interface_status <= STATUS_WRITE_BACK_END;
      if(!sdram_ack_buff)begin
        interface_status <= STATUS_READ_CACHE;
      end
    end
    //default: begin
    //  interface_status <= STATUS_INIT;
    //  ready <= 0;
    //end
    endcase

    if(sdram_ack_buff && sdram_req)begin
      sdram_req <= 0;
    end

  end
end


endmodule
